﻿using System.Linq;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System;
using Microsoft.Xna.Framework.Content;
using System.Diagnostics;

namespace DARE
{
    public enum EAnimState : byte
    {
        STOP = 0,
        RUN = 1,
        LOOP = 2,
        REVERSE = 4,
        PAUSE = 8,
    }

    public delegate void AnimationEvent<T>(T sender);

    public sealed class CAnimConfig
    {
        #region Fields

        private readonly int m_animCount = 0;
        private readonly Dictionary<int, int> m_indexer;

        private Dictionary<int, List<Action<CAnimConfig, int>>> m_registerEvent;
        private Dictionary<int, List<Action<CAnimConfig, int>>> m_oldRaisingEvent;
        private int m_oldKey = -1;

        //private int m_startId;

        //private int m_endId;

        private IAnimation m_anim;

        [ContentSerializer]
        private double m_startTime;

        [ContentSerializer]
        private double m_endTime;


        private List<CTransform> m_animTransfom;

        private float m_blendfactor = 1.0f;
        private EAnimState m_state;

        internal int m_lastIndex = 0; // try to optimise with this champ
        internal int m_target = 0;
        internal int m_incre = 1;

        #endregion

        #region Properties

        internal IAnimation Animation
        {
            get { return this.m_anim; }
            set
            {
                if (m_anim != value)
                {
                    m_anim = value;
                }
            }
        }

        public TimeSpan Duration
        {
            get { return this.m_anim.Duration; }
        }

        public float Speed { get; set; }

        public EAnimState State
        {
            get { return m_state; }
            set
            {
                if (value != m_state)
                {
                    if ((m_state & EAnimState.REVERSE) != (value & EAnimState.REVERSE))
                    {
                        m_incre *= -1;
                        if ((value & EAnimState.REVERSE) == EAnimState.REVERSE)
                        {
                            m_target = -1;
                            m_lastIndex = m_anim.Length - 1;
                        }
                        else
                        {
                            m_target = m_anim.Length;
                            m_lastIndex = 0;
                        }
                    }
                    if (!((value & EAnimState.PAUSE) == EAnimState.PAUSE)
                        && (value & EAnimState.RUN) == EAnimState.STOP
                        && (m_state & EAnimState.RUN) == EAnimState.RUN)
                    {
                        AnimationStoped = true;
                        //for (int i = 0; i < m_animCount; ++i)
                        //{
                        //    m_animTransfom[i] = null;
                        //}
                        if (AnimationEnded != null)
                            AnimationEnded.Invoke(this);
                    }
                    else
                        AnimationStoped = false;
                    m_state = value;
                }
            }
        }

        [ContentSerializer]
        public string AnimName { get; private set; }

        public bool AnimationStoped { get; private set; }

        internal List<CTransform> Transforms { get { return m_animTransfom; } }

        public double Elapsed { get; set; }

        public float BlendFactor
        {
            get { return this.m_blendfactor; }
            set
            {
                if (value >= 0 && value <= 1.0f)
                    this.m_blendfactor = value;
            }
        }

        public bool Priority { get; internal set; }

        #endregion

        #region Event

        public event AnimationEvent<CAnimConfig> AnimationEnded;

        #endregion

        #region CTOR

        internal CAnimConfig()
        {
            m_indexer = new Dictionary<int, int>();
            m_animTransfom = new List<CTransform>(m_animCount);
        }

        //public CAnimConfig(IAnimation anim, string n_name, int startId)
        //    : this(anim, n_name, startId, -1)
        //{

        //}

        //public CAnimConfig(IAnimation anim, string n_name)
        //    : this(anim, n_name, 0, -1)
        //{

        //}

        public CAnimConfig(IAnimation anim, string n_name, int startId, int endId)
            : this(anim, n_name, startId, endId, null as List<int>)
        {
            this.AnimName = n_name;
        }

        internal CAnimConfig(IAnimation anim, string n_name, double start, double endId)
            : this(anim, n_name, start, endId, null)
        {

        }

        internal CAnimConfig(IAnimation anim, string n_name, double start, double endId, List<int> _skeleton)
        {
            m_indexer = new Dictionary<int, int>();
            if (_skeleton != null)
            for (int i = 0; i < _skeleton.Count; ++i)
                m_indexer.Add(_skeleton[i], i);
            m_anim = anim;
            m_animCount = anim.Hierarchy.NodesCount;
            State = EAnimState.STOP;
            m_animTransfom = new List<CTransform>(m_animCount);
            m_target = m_anim.Length;
            //AnimName = anim.Name;

            this.AnimName = n_name;

            m_startTime = start;// anim.GetTimeByFrameId((int)start);
            m_endTime = endId;// anim.GetTimeByFrameId((int)endId);
        }

        internal CAnimConfig(IAnimation anim, string n_name, int startId)
            : this(anim, n_name, startId, -1, null)
        {

        }

        internal CAnimConfig(IAnimation anim, string n_name)
            : this(anim, n_name, 0, -1, null)
        {

        }

        internal CAnimConfig(IAnimation anim, string n_name, int startId, int endId, List<int> _skeleton)
            : this(anim, n_name, anim.GetTimeByFrameId(startId), anim.GetTimeByFrameId(endId), _skeleton)
        {

        }

        #endregion

        #region Method

        public void CutAnimation(int duration)
        {
            Animation.CutAnimation(duration);
            m_target = m_anim.Length;
            m_endTime = m_anim.Duration.TotalMilliseconds;
        }

        internal void Update(GameTime time)
        {
            float timeSinceLastFrame = CDare.Instance.Config.Time.TimeSinceLastFrame;

            if ((State & EAnimState.RUN) == EAnimState.STOP || (State & EAnimState.PAUSE) == EAnimState.PAUSE)
                return;
            if (CDare.Instance.Config.Time.IsPaused)
                return;
            if ((State & EAnimState.REVERSE) == EAnimState.REVERSE)
            {
                Elapsed -= (timeSinceLastFrame * Speed);
                if (Elapsed < m_startTime)
                {
                    if ((State & EAnimState.LOOP) == EAnimState.LOOP)
                    {
                        Elapsed = m_endTime + -(-Elapsed % (m_endTime - m_startTime));
                        m_lastIndex = m_anim.Length - 1;
                    }
                    else
                    {
                        State = EAnimState.STOP;
                        return;
                    }
                }
            }
            else
            {
                Elapsed += (timeSinceLastFrame * Speed);
                if (Elapsed > m_endTime)
                {
                    if ((State & EAnimState.LOOP) == EAnimState.LOOP)
                    {
                        Elapsed = (Elapsed - m_endTime) % (m_endTime - m_startTime);
                        m_lastIndex = 0;
                    }
                    else
                    {
                        State = EAnimState.STOP;
                        return;
                    }
                }
            }

            int _previousKey = 0;
            int _nextKey = 0;

            m_animTransfom = m_anim.GetFrame(this, out _previousKey, out _nextKey);
            if (m_registerEvent != null)
            {

                if (m_oldRaisingEvent == null)
                    m_oldRaisingEvent = new Dictionary<int, List<Action<CAnimConfig, int>>>();

                int _key = -1;
                Debug.Assert(_previousKey != -1 && _nextKey != -1);
                if (Math.Abs(_previousKey - _nextKey) == 2)
                {
                    _key = (_previousKey + _nextKey / 2);
                }
                else
                    _key = _nextKey;

                if (m_oldKey == _key)
                    return;

                m_oldKey = _key;

                if (m_registerEvent.ContainsKey(_key))
                {

                    foreach (var elem in m_registerEvent[_key])
                    {
                        //if (m_oldRaisingEvent.ContainsKey(_key))
                        //{
                        //    if (m_oldRaisingEvent[_key].Contains(elem))
                        //        continue;
                        //}
                        //else
                        //    m_oldRaisingEvent.Add(_key, new List<Action<CAnimConfig,int>>());
                        try
                        { elem.Invoke(this, _key); }
                        catch (Exception ex)
                        { CDare.Instance.DebugMgr.DebugConsole.WriteLine(ex.Message, CDebugConsole.EMessageType.ERROR); }

                        //m_oldRaisingEvent[_key].Add(elem);
                    }
                }
            }
        }

        /// <summary>
        /// TO TEST
        /// </summary>
        /// <param name="state"></param>
        public void Reset(EAnimState state)
        {
            Elapsed = 0;
            State = state;
            if ((state & EAnimState.REVERSE) == EAnimState.REVERSE)
                Elapsed = m_endTime;
            else
                Elapsed = 0;
        }

        public CTransform this[int index]
        {
            get
            {
                if (!m_indexer.ContainsKey(index))
                    return null;
                if (m_animTransfom.Count == 0)
                    return null;
                return m_animTransfom[m_indexer[index]];
            }
        }

        public object Clone()
        {
            return AnimClone();
    }

        internal void UpdateIndex(List<int> _skeleton)
        {
            if (_skeleton != null)
            {
                for (int i = 0; i < _skeleton.Count; ++i)
                    m_indexer.Add(_skeleton[i], i);
}
        }

        public CAnimConfig AnimClone()
        {
            CAnimConfig _conf = new CAnimConfig(m_anim, m_anim.Name, m_startTime, m_endTime);
            //_conf.UpdateIndex((from el in m_indexer select el.Key).ToList());
            _conf.m_blendfactor = this.m_blendfactor;
            _conf.m_state = this.m_state;
            _conf.Speed = this.Speed;
            return _conf;
        }

        public void RegisterKeyFrameEvent(int KeyFrame, Action<CAnimConfig, int> _event)
        {
            if (m_registerEvent == null)
                m_registerEvent = new Dictionary<int, List<Action<CAnimConfig, int>>>();

            if (!m_registerEvent.ContainsKey(KeyFrame))
                m_registerEvent.Add(KeyFrame, new List<Action<CAnimConfig, int>>());
            if (!m_registerEvent[KeyFrame].Contains(_event))
                m_registerEvent[KeyFrame].Add(_event);
        }

        public void UnregisterKeyFrameEvent(int keyFrame, Action<CAnimConfig, int> _event)
        {
            if (m_registerEvent == null)
                return;

            if (m_registerEvent.ContainsKey(keyFrame))
            {
                m_registerEvent[keyFrame].Remove(_event);
                if (m_registerEvent[keyFrame].Count <= 0)
                {
                    m_registerEvent.Remove(keyFrame);
                    if (m_registerEvent.Count <= 0)
                        m_registerEvent = null;
                }
            }
        }

        #endregion
    }
}
